﻿' EventSink.cs
' compile with: /doc:EventSink.xml
' <copyright>Copyright (c) Microsoft Corporation. All rights reserved.
' </copyright>
' <summary>This file contains the implementation for the EventSink 
' class which handles events from Visio.</summary>

Imports System
Imports System.Diagnostics
Imports Microsoft.Office.Interop.Visio
Imports Visio = Microsoft.Office.Interop.Visio
Imports Microsoft.Office.Interop.VisOcx

Imports Microsoft.Office.Interop.Visio.tagVisUICmds

Imports System.Diagnostics.CodeAnalysis




    ''' <summary>VisioEventHandler is an event delegate used for raising 
    ''' events. Events are used to decouple this class from the 
''' NetworkPlanSampleForm class.</summary>
    ''' 
    ''' <param name="sender">The Visio object that raised the event</param>
    ''' <param name="e">The arguments associated with the event</param>
''' Suppress this warning because generic event handler doesn't work here. 
''' Generic event handler uses parameter "sender" to differentiate the handlers,
''' but "OnShapeAdd" and "OnShapeDelete" handlers both use the "sender" to pass
''' a Visio shape. 
    <SuppressMessage("Microsoft.Design", "CA1003:UseGenericEventHandlerInstances")> _
    Public Delegate Sub VisioEventHandler(ByVal sender As Object, ByVal e As EventArgs)

    ''' <summary>The EventSink class handles events from Visio
    ''' which are specified in the AddAdvise method.</summary>
    <System.Runtime.InteropServices.ComVisible(True)> _
    Public NotInheritable Class EventSink
        Implements IVisEventProc



#Region " Properties and Definitions "


        '// <summary>visEvtAdd is declared as a 2-byte value to avoid a run-time
        '// overflow error.</summary>
    Private Const visEvtAdd As Short = -32768

    Private Const networkPlanArgument As String = "/networkplan"
    Private Const doubleClickCommand As String = "/cmd=1"

        ''' <summary>Visio.Application object.</summary>
    Private eventApplication As Global.Microsoft.Office.Interop.Visio.Application

        ''' <summary>Visio.Document object.</summary>
    Private eventDocument As Document

        ''' <summary>Two FIFO queues are used to store added and deleted
        ''' shape information while Visio events are being processed.</summary>
    Private shapeAddedQueue As System.Collections.Queue
    Private shapeDeletedQueue As System.Collections.Queue

        ''' <summary>OnShapeAdd event is raised when a shape is
        ''' added to the drawing.</summary>
    Public Event OnShapeAdd As VisioEventHandler

        ''' <summary>OnShapeDelete event is raised when a shape is
        ''' deleted from the drawing.</summary>
    Public Event OnShapeDelete As VisioEventHandler

        ''' <summary>OnShapeDoubleClick event is raised when the user
        ''' double-clicks a shape from the sample office furniture stencil.
        ''' </summary>
    Public Event OnShapeDoubleClick As VisioEventHandler


    Public lngCurrentScope As Int32 = -1

    Private Shared _lastShape As Shape
    Public ReadOnly Property lastShape() As Shape
        Get
            Return Me._lastShape
        End Get
    End Property

    Private Shared _lastEvent As Integer
    Public ReadOnly Property lastEvent() As Integer
        Get
            Return Me._lastEvent
        End Get
    End Property


#End Region


#Region " visio event capturing "

    ''' <summary>The EventSink default constructor creates the queues that
    ''' will be used to hold added and deleted shapes for processing.</summary>
    Public Sub New()

        ' Create the added and deleted queues. They will be cleaned
        ' up when the EventSink is deleted.
        shapeAddedQueue = New System.Collections.Queue
        shapeDeletedQueue = New System.Collections.Queue


    End Sub

    ''' <summary>AddAdvise method takes the Visio application
    ''' and document and adds events for this solution to
    ''' their event lists.</summary>
    ''' <param name="callingApplication">The Visio application that
    ''' the user will be working with.</param>
    ''' <param name="callingDocument">The Visio document that the user
    ''' will be working with.</param>
    <CLSCompliant(False)> _
    Public Sub AddAdvise _
        (ByVal callingApplication As Global.Microsoft.Office.Interop.Visio.Application, _
         ByVal callingDocument As Document)

        ' The calling application must exist.
        If callingApplication Is Nothing Then

            ' Throw a meaningful error.
            Throw New ArgumentNullException("callingApplication", Utility.GetResourceString(Strings.NullApplicationError))
        End If

        ' The calling document must exist.
        If callingDocument Is Nothing Then

            ' Throw a meaningful error.
            Throw New ArgumentNullException("callingDocument", Utility.GetResourceString(Strings.NullDocumentError))
        End If

        ' Save the application for setting the events.
        eventApplication = callingApplication

        ' Save the document for setting the events.
        eventDocument = callingDocument

        ' Add events of interest.
        setAddAdvise()

    End Sub

    ''' <summary>The IVisEventProc.VisEventProc function is called by
    ''' Visio when an event which has been added to an events list
    ''' collection has been raised. The events were added to the
    ''' event lists in the SetAddAdvise method.</summary>
    ''' <param name="eventCode">Event code of the event that raised.
    ''' </param>
    ''' <param name="source">Reference to the object containing the
    ''' event that raised. This can be either an application or a
    ''' document, since both had events added to their eventlist.
    ''' </param>
    ''' <param name="eventId">Unique identifier of the event that
    ''' raised. This parameter is ignored in this case.</param>
    ''' <param name="eventSequenceNumber">The sequence of the event
    ''' relative to events that have raised so far. This parameter
    ''' is ignored in this case.</param>
    ''' <param name="subject">Reference to the object to which the
    ''' event occurred. For shape delete and add events, for example,
    ''' the subject is the shape.</param>
    ''' <param name="moreInfo">Additional information about the event.
    ''' This parameter is ignored in this case.</param>
    ''' <returns>The return value is ignored unless the event is a
    ''' query event. Since this sample does not process query events
    ''' an empty object is returned.</returns>
    Private Function VisEventProc _
        (ByVal eventCode As Short, _
         ByVal source As Object, _
         ByVal eventId As Integer, _
         ByVal eventSequenceNumber As Integer, _
         ByVal subject As Object, _
         ByVal moreInfo As Object) _
         As Object Implements IVisEventProc.VisEventProc

        Dim eventProcApplication As Global.Microsoft.Office.Interop.Visio.IVApplication = TryCast(source, IVApplication)

        ' Check for each event code that is handled. The event
        ' codes are a combination of an object and an action.
        ' Only the events added in the SetAddAdvise method will
        ' be sent to this method, and only those events need to
        ' be included in this switch statement.
        Dim eventShape As Shape = Nothing
        If (eventCode And CShort(VisEventCodes.visEvtShape)) > 0 Then
            eventShape = DirectCast(subject, Shape)
        End If

        Dim subjectPage As Microsoft.Office.Interop.Visio.Page

        If eventSequenceNumber = lastEvent Then
            'double handling the event 
            GoTo ExitFunction
        End If

        Select Case eventCode

            ' 
            ' shape level events
            '
            Case CShort(VisEventCodes.visEvtShape) + CShort(visEvtAdd)

                ' Handle the add-shape event.
                handleShapeAdd(eventShape)
                Exit Select

            Case CShort(VisEventCodes.visEvtDel) + CShort(VisEventCodes.visEvtShape)

                ' Handle the delete-shape event.
                handleShapeDelete(eventShape)
                Exit Select

                '
                ' application level events
                '
            Case CShort(VisEventCodes.visEvtApp) + CShort(VisEventCodes.visEvtMarker)

                ' Handle this marker event.
                handleMarker(eventProcApplication)
                Exit Select

            Case CShort(VisEventCodes.visEvtApp) + CShort(VisEventCodes.visEvtNonePending)

                ' Handle the no-events-pending event.
                handleNonePending()
                Exit Select


                '
                ' page level events
                '
            Case CShort(Microsoft.Office.Interop.Visio.VisEventCodes. _
                        visEvtPage) + visEvtAdd
                ' page add event 
                ' Subject object is a Page
                subjectPage = CType(subject, Microsoft.Office.Interop. _
                    Visio.Page)

                handlePageAdd(subjectPage)

                Exit Select

            Case CShort(Microsoft.Office.Interop.Visio.VisEventCodes. _
                    visEvtPage) + CShort(VisEventCodes.visEvtDel)
                ' page delete event 
                ' Subject object is a Page
                subjectPage = CType(subject, Microsoft.Office.Interop. _
                    Visio.Page)

                handlePageDelete(subjectPage)

                Exit Select

            Case CShort(VisEventCodes.visEvtApp) + CShort(VisEventCodes.visEvtCodeBefWinPageTurn)
                ' Subject object is a Page
                ' page turned event 
                subjectPage = CType(subject, Microsoft.Office.Interop. _
                    Visio.Page)

                handlePageTurn(subjectPage, "exit")

                Exit Select


            Case CShort(VisEventCodes.visEvtApp) + CShort(VisEventCodes.visEvtCodeWinPageTurn)
                ' Subject object is a Page
                ' page turned event 
                subjectPage = CType(subject, Microsoft.Office.Interop. _
                    Visio.Page)

                handlePageTurn(subjectPage, "enter")

                Exit Select



            Case Else

                Exit Select
        End Select

        _lastEvent = eventSequenceNumber

ExitFunction:

        Return Nothing

    End Function

    ''' <summary>The SetAddAdvise method adds the events that need to
    ''' be responded to with solution-specific behavior to the Visio
    ''' objects. For this sample, the add and delete shape events are
    ''' handled so that the data in the grid can be kept consistent
    ''' with the drawing. Marker events are handled to respond to shape
    ''' double-click from shapes in the sample office furniture stencil.
    ''' The none-pending event is also handled so that the add and delete
    ''' processing will only be done when Visio is idle.</summary>
    ''' <remarks>An Exception is thrown on failure.</remarks>
    Private Sub setAddAdvise()

        Try


            ' The Sink and TargetArgs values aren't needed.
            Const sink As String = ""
            Const targetArgs As String = ""

            Dim applicationEvents As EventList = eventApplication.EventList
            Dim documentEvents As EventList = eventDocument.EventList

            ' Add the shape-added event to the document. The new shape
            ' will be available for processing in the handler. The
            ' value for VisEventCodes.visEvtAdd cannot be
            ' automatically converted to a short type, so the
            ' unchecked function is used. This allows the addition to
            ' be done and returns a valid short value.
            documentEvents.AddAdvise _
                    ((CShort(visEvtAdd) + CShort(VisEventCodes.visEvtShape)), _
                     DirectCast(Me, IVisEventProc), _
                     sink, targetArgs)

            ' Add the before-shape-deleted event to the document. This 
            ' event will be raised when a shape is deleted from the
            ' document. The deleted shape will still be available for
            ' processing in the handler.
            documentEvents.AddAdvise _
                (CShort(VisEventCodes.visEvtDel) + CShort(VisEventCodes.visEvtShape), _
                 DirectCast(Me, IVisEventProc), sink, targetArgs)

            ' Add marker events to the application. This event
            ' will be raised when a user double-clicks a shape
            applicationEvents.AddAdvise _
                (CShort(VisEventCodes.visEvtApp) + CShort(VisEventCodes.visEvtMarker), _
                 DirectCast(Me, IVisEventProc), _
                 sink, targetArgs)

            ' Add the none-pending event to the application. This
            ' event will be raised when Visio is idle.
            applicationEvents.AddAdvise _
                (CShort(VisEventCodes.visEvtApp) + CShort(VisEventCodes.visEvtNonePending), _
                 DirectCast(Me, IVisEventProc),  _
                sink, targetArgs)



            '
            ' page events
            '
            ' Add the WindowTurnToPage event.
            applicationEvents.AddAdvise(CShort(Microsoft.Office.Interop.Visio. _
                VisEventCodes.visEvtCodeWinPageTurn), _
                 DirectCast(Me, IVisEventProc), _
                sink, targetArgs)

            ' Add the PageAdded event.
            documentEvents.AddAdvise(CShort(Microsoft.Office.Interop.Visio. _
                VisEventCodes.visEvtPage + visEvtAdd), _
                DirectCast(Me, IVisEventProc), _
                sink, targetArgs)

            ' Add the PageDeleted event.
            documentEvents.AddAdvise(CShort(Microsoft.Office.Interop.Visio. _
                VisEventCodes.visEvtPage + VisEventCodes.visEvtDel), _
                DirectCast(Me, IVisEventProc), _
                sink, targetArgs)

            ' Add the Window before Turn to Page event.
            ' capture page setup for page turns
            applicationEvents.AddAdvise( _
                Visio.VisEventCodes.visEvtCodeBefWinPageTurn, _
                DirectCast(Me, IVisEventProc), _
                sink, targetArgs)


        Catch ex As Exception

            Dim errWin As ErrorWindow = New ErrorWindow("setAddAdvise", ex)
            errWin.ShowDialog()

        End Try

    End Sub


#End Region


#Region " Event Filtering "


    ''' <summary>The handleNonePending method is called when all Visio
    ''' events have been processed. The queued shape adds and deletes
    ''' are processed during Visio's idle time.</summary>
    Private Sub handleNonePending()

        ' Process the added-shapes queue.
        If shapeAddedQueue IsNot Nothing Then

            ' Raise an OnShapeAdd event for each shape in the queue.
            While shapeAddedQueue.Count > 0

                RaiseEvent OnShapeAdd(shapeAddedQueue.Dequeue(), New EventArgs())

            End While
        Else

            ' There are no event listeners so just empty the queue.
            shapeAddedQueue.Clear()
        End If

        ' Process the deleted-shapes queue.
        If shapeDeletedQueue IsNot Nothing Then

            ' Raise an OnShapeDelete event for each shape in the 
            ' queue.
            While shapeDeletedQueue.Count > 0

                RaiseEvent OnShapeDelete(shapeDeletedQueue.Dequeue(), New EventArgs())

            End While
        Else

            ' There are no event listeners so just empty the queue.
            shapeDeletedQueue.Clear()
        End If


    End Sub

    ''' <summary>The handleShapeAdd method is called when a shape
    ''' is added. The shape exists, so the shape reference can be
    ''' queued for use later. The actual processing of the add will
    ''' be done when all Visio events have been processed.</summary>
    ''' <param name="addedShape">The shape being added.</param>
    Private Sub handleShapeAdd(ByVal addedShape As Shape)


        If addedShape Is Nothing Then Exit Sub

        Dim strMasterName As String = ""
        Try
            Dim mstrShape As Visio.Master = addedShape.Master
            If mstrShape IsNot Nothing Then
                strMasterName = addedShape.Master.NameU.ToString
            Else
                strMasterName = "NoMasterShape"
            End If
        Catch
        End Try

        ' if we find a master name with a period the fix it
        Dim intPos As Int16 = InStr(strMasterName, ".")
        If 0 < intPos Then
            strMasterName = Mid(strMasterName, 1, intPos - 1)
        End If

        ' if we are in a create drawing then get out
        ' allows us to control whether we react to the shape added event
        If eventApplication.IsInScope(lngCurrentScope) Then
            '   Debug.Print("shaped added during draw recordset")

            ' was this shape added because of a group command
        ElseIf eventApplication.IsInScope(visCmdObjectGroup) Then
            'Debug.Print("Shape added using grouping action. " _
            '    & "Master name of the shape - " _
            '    & strMasterName)

            ' is in scope with apply data graphic
        ElseIf eventApplication.IsInScope(visCmdApplyDataGraphic) Then
            'Debug.Print("Shape Added using Apply Data Graphic")

            ' IsInScope with visCmdUFEditPaste parameter returns
            ' True if the shape is added using EditPaste action.
        ElseIf eventApplication.IsInScope(visCmdUFEditPaste) Then
            'Debug.Print("Shape added using EditPaste action. " _
            '    & "Master Name of the Shape - """ _
            '    & strMasterName)

            ' IsInScope with visCmdEditPasteSpecial parameter
            ' returns True if the shape is added using
            ' EditPasteSpecial action.
        ElseIf eventApplication.IsInScope(visCmdEditPasteSpecial) Then
            'Debug.Print("Shape added using EditPasteSpecial " _
            '    & "action. Master name of the shape - " _
            '    & strMasterName)
        Else

            If lastShape Is Nothing Then
                _lastShape = addedShape
            Else
                ' make sure this is not a double interrupt
                If lastShape IsNot addedShape Then
                    _lastShape = addedShape
                Else
                    Exit Sub
                End If
            End If
            shapeAddedQueue.Enqueue(addedShape)
        End If



    End Sub

    ''' <summary>The handleShapeDelete method is called when a shape
    ''' is being deleted. The shape still exists, so the information
    ''' can be read from the shape for use later. The actual
    ''' processing of the delete will be done when all Visio events
    ''' have been processed.</summary>
    ''' <param name="deletedShape">The shape being deleted.</param>
    Private Sub handleShapeDelete(ByVal deletedShape As IVShape)

        ' Get the product ID from the shape that is being deleted.
        Dim productId As Integer = Utility.ProductId(deletedShape)

        ' If this is a valid productId, the shape information must
        ' be removed from the data grid.
        If productId <> ProductData.InvalidProductId Then

            ' Add the product ID to the queue.
            shapeDeletedQueue.Enqueue(productId)
        Else

            ' Recurse into sub shapes to handle product shapes that may be
            ' contained within group shapes.
            For index As Integer = 1 To deletedShape.Shapes.Count
                handleShapeDelete(deletedShape.Shapes(index))
            Next
        End If


    End Sub

    ''' <summary>The handleMarker method is called when Visio raises
    ''' a marker event. When the user double-clicks on a shape from the
    ''' sample office furniture stencil the formula in the shape's 
    ''' EventDblClick cell will run the QueueMarkerEvent addon which will
    ''' raise a marker event.</summary>
    ''' <param name="visioApplication">The Visio application that raised
    ''' this event.</param>
    <SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")> _
    Private Sub handleMarker(ByVal visioApplication As IVApplication)

        Dim arguments As String
        Dim targetShape As Shape

        'If the arguments include /networkplan /cmd=1
        'then get a reference to the shape and raise the
        'OnShapeDoubleClick event

        arguments = visioApplication.EventInfo(CShort(VisEventCodes.visEvtIdMostRecent))
        arguments = arguments.ToLowerInvariant()

        ' If this marker event was caused by double-clicking a 
        ' shape from the sample office furniture stencil then
        ' raise an OnShapeDoubleClick event
        If (arguments.IndexOf(networkPlanArgument, StringComparison.Ordinal) >= 0) _
            AndAlso (arguments.IndexOf(doubleClickCommand, StringComparison.Ordinal) >= 0) Then

            ' Get a reference to this shape
            targetShape = Utility.GetShapeFromArguments(visioApplication, arguments)

            ' Raise an OnShapeDoubleClick event for this shape.
            RaiseEvent OnShapeDoubleClick(targetShape, New EventArgs())

        End If


    End Sub



    Private Sub handlePageTurn _
        (ByVal subjectPage As Microsoft.Office.Interop.Visio.Page, _
         ByVal operationString As String)

        Select Case operationString
            Case "enter"
                Exit Select
            Case "exit"
                Exit Select
        End Select

    End Sub

    Private Sub handlePageAdd _
        (ByVal addedPage As Microsoft.Office.Interop.Visio.Page)

    End Sub

    Private Sub handlePageDelete _
        (ByVal deletedPage As Microsoft.Office.Interop.Visio.Page)

    End Sub


#End Region



End Class
